package ar.edu.unq.dodb.morphia.store;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;

import ar.edu.unq.dodb.utils.ReflectionUtils;

import com.google.code.morphia.annotations.Property;
import com.google.code.morphia.annotations.Reference;
import com.google.code.morphia.mapping.Mapper;
import com.google.code.morphia.query.Query;
import com.google.code.morphia.query.UpdateOpsImpl;
import com.mongodb.DBObject;

public class BasicHome<E> implements Home<E> {
	private final Class<E> type;

	@SuppressWarnings("unchecked")
	public BasicHome() {
		final ParameterizedType sc = (ParameterizedType) this.getClass()
				.getGenericSuperclass();
		this.type = (Class<E>) sc.getActualTypeArguments()[0];

	}

	public BasicHome(final Class<E> entityType) {
		this.type = entityType;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void actualizar(final E obj) {
		final E incoming = this.getDBObject(obj);
		final E fromDB = MorphiaUtils.dataStore().get(obj);
		this.updateProperties(obj, incoming, fromDB);
		final Field[] references = ReflectionUtils.getAllFieldsAnnotatedWith(
				obj, Reference.class);

		for (final Field field : references) {
			final Object ref = ReflectionUtils.fieldGet(obj, field);
			@SuppressWarnings({ "rawtypes" })
			final BasicHome tempHome = new BasicHome(ref.getClass());
			tempHome.insertar(ref);
		}
	}

	private E getDBObject(final E obj) {
		final DBObject dbObject = MorphiaUtils.getMorphia().toDBObject(obj);
		return MorphiaUtils.getMorphia().fromDBObject(this.type, dbObject,
				MorphiaUtils.getCache());
	}

	private UpdateOpsImpl<E> getUpdateOperations() {
		final Mapper mapper = MorphiaUtils.getMorphia().getMapper();
		return new UpdateOpsImpl<E>(this.type, mapper);

	}

	@Override
	public E innerSearch(final String innerField, final Object val) {
		final Query<E> query = MorphiaUtils.dataStore().createQuery(this.type);
		query.filter(innerField + " =", val);
		final E foo = query.get();
		if (foo == null)
			return null;
		return this.getDBObject(foo);
	}

	@Override
	public void insertar(final E obj) {
		final E foo = this.getDBObject(obj);
		MorphiaUtils.dataStore().save(foo);
	}

	@Override
	public E obtener(final Object id) {
		final E foo = MorphiaUtils.dataStore().get(this.type, id);
		if (foo == null)
			return null;
		return this.getDBObject(foo);
	}

	@Override
	public E obtenerPorEquals(final String field, final Object val) {
		final E foo = MorphiaUtils.dataStore().createQuery(this.type)
				.field(field).equal(val).get();
		if (foo == null)
			return null;
		return this.getDBObject(foo);
	}

	private void updateProperties(final E obj, final E incoming, final E fromDB) {
		final Field[] properties = ReflectionUtils.getAllFieldsAnnotatedWith(
				incoming, Property.class);
		for (int i = 0; i < properties.length; i++) {
			final Field field = properties[i];
			final Object incomingValue = ReflectionUtils.fieldGet(incoming,
					field);
			final Object dbValue = ReflectionUtils.fieldGet(fromDB, field);
			if (!incomingValue.equals(dbValue)) {
				final UpdateOpsImpl<E> ops = this.getUpdateOperations();
				ops.set(field.getName(), incomingValue);
				MorphiaUtils.dataStore().update(obj, ops);
			}
		}
	}

}
